using System;
using System.Text;
using CLELCore.Executable;

namespace CLELCore.Assembly.Parser.ValidAssemblyParser
{
	public class CLELValidAssemblyParser : ICLELParser
	{
		private CLELAssembly _asm;
		private ICLELReader _reader;
		private int _file_offset;
		private int _rva;
		private CLELLogger _log;

		private short _machine_type;

		public CLELValidAssemblyParser(ICLELReader reader,CLELLogger log)
		{
			_reader = reader;
			_file_offset = 0;
			_rva = 0;
			_log = log;
		}

		public void Parse(CLELAssembly asm)
		{
			_asm = asm;
			ParseDOSHeader();
			//PE\0\0
			byte[] signature = _asm.Signature;
			if(signature != null && signature.Length == 4 && 
				signature[0] == 0x50 && signature[1] == 0x45 && 
				signature[2] == 0x0 && signature[3] == 0x0)
			{
				ParsePEHeader();
			}
			else
			{
				throw new CLELParserException("Error unknown signature: "+ ASCIIEncoding.ASCII.GetString(signature));
			}

			ParseStandardFields();
			ParseNTSpecific();
			ParseDataDirectories();
			ParseSectionHeaders();
			ParseSections();
		}

		private void ParseDOSHeader()
		{
			_log.WriteLine("CLEL - using CLELValidAssemblyParser");
			_log.WriteLine("Parsing - " + _reader.Path);
			_log.WriteLine(DateTime.Now.ToString()+"\n");

			DOSHeader dosheader = new DOSHeader();

			//read lfanew and machine type in little endian
			_reader.IsLittleEndian = true;
			//read everything up to lfanew
			byte [] dos_fields = new byte[DOSHeader.LFANEW_OFFSET];
			int ret = _reader.Read(dos_fields,0,DOSHeader.LFANEW_OFFSET);
			if(ret != DOSHeader.LFANEW_OFFSET)
			{
				_log.WriteLine("Error reading dos fields: " + ret);
				throw new CLELParserException("Error reading dos fields: "+ret);
			}

			//read lfanew
			byte [] lfanew_buffer = new byte[4];
			ret = _reader.Read(lfanew_buffer,0,4);
			if(ret != 4)
			{
				_log.WriteLine("Error reading lfanew: " + ret);
				throw new CLELParserException("Error reading lfanew: "+ret);
			}
			int lfanew = Bits.MakeInt(lfanew_buffer[0],lfanew_buffer[1],lfanew_buffer[2],lfanew_buffer[3]);
			
			//read from lfanew to end of code
			int len = lfanew - DOSHeader.LFANEW_OFFSET-4;
			byte [] dos_code = new byte[len];
			ret = _reader.Read(dos_code,0,len);
			if(ret != len)
			{
				_log.WriteLine("Error reading dos header code: " + ret);
				throw new CLELParserException("Error reading dos header code: "+ret);
			}

			//read signature
			byte temp = 0;
			ret = _reader.ReadByte(ref temp);
			if(ret != 1)
			{
				_log.WriteLine("Error reading signature: " + ret);
				throw new CLELParserException("Error reading signature: "+ret);
			}
			int signature_length = 0;
			//PE\0\0
			if(temp == 0x50)
				signature_length = 4;
			else
				signature_length = 2;
			byte [] signature = new byte[signature_length];
			signature[0] = temp;
			ret = _reader.Read(signature,1,signature_length-1);
			if(ret != signature_length-1)
			{
				_log.WriteLine("Error reading rest of signature: " + ret);
				throw new CLELParserException("Error reading rest of signature: "+ret);
			}
			_asm.Signature = signature;

			//read machine type to find the endiness
			ret = _reader.ReadShort(ref _machine_type);
			if(ret != 2)
			{
				_log.WriteLine("Error reading machine type: " + ret);
				throw new CLELParserException("Error reading machine type: "+ret);
			}
			if(_machine_type != MachineType.Image_File_Machine_I386)
			{
				_log.WriteLine("Error unknown machine type: " + _machine_type);
				throw new CLELParserException("Error unknown machine type: "+_machine_type);
			}

			//put code to recognize rest of machine types
			bool little_endian;
			if(_machine_type == MachineType.Image_File_Machine_I386)
			{
				little_endian = true;
			}
			else
			{
				little_endian = false;
			}
			//set files endiness type
			_reader.IsLittleEndian = little_endian;

			//merge arrays so that DOSHeader can be parsed
			byte [] buffer = new byte[dos_fields.Length+dos_code.Length+lfanew_buffer.Length];
			dos_fields.CopyTo(buffer,0);
			lfanew_buffer.CopyTo(buffer,dos_fields.Length);
			dos_code.CopyTo(buffer,dos_fields.Length+lfanew_buffer.Length);
			//parse dos header
			dosheader = new DOSHeader();

			ParseDOSHeaderFields(buffer,dosheader);
			_asm.DOSHeader = dosheader;
			_file_offset += buffer.Length;
			_file_offset += signature_length;
		}

		private void ParseDOSHeaderFields(byte [] buffer,DOSHeader dosheader)
		{
			dosheader.MagicNumber = Endiness.ShortBigToLittleEndian(buffer[0],buffer[1]);
			
			if(_reader.IsLittleEndian)
			{
				dosheader.LastPageOfFile = Endiness.ShortLittleToBigEndian(buffer[2],buffer[3]);
				dosheader.PagesInFile = Endiness.ShortLittleToBigEndian(buffer[4],buffer[5]);
				dosheader.Relocations = Endiness.ShortLittleToBigEndian(buffer[6],buffer[7]);
				dosheader.SizeOfHeader = Endiness.ShortLittleToBigEndian(buffer[8],buffer[9]);
				dosheader.MinParagraphs = Endiness.ShortLittleToBigEndian(buffer[10],buffer[11]);
				dosheader.MaxParagraphs = Endiness.ShortLittleToBigEndian(buffer[12],buffer[13]);
				dosheader.InitialSSValue = Endiness.ShortLittleToBigEndian(buffer[14],buffer[15]);
				dosheader.InitialSPValue = Endiness.ShortLittleToBigEndian(buffer[16],buffer[17]);
				dosheader.Checksum = Endiness.ShortLittleToBigEndian(buffer[18],buffer[19]);
				dosheader.InitialIPValue = Endiness.ShortLittleToBigEndian(buffer[20],buffer[21]);
				dosheader.InitialCSValue = Endiness.ShortLittleToBigEndian(buffer[22],buffer[23]);
				dosheader.RelocationsTable = Endiness.ShortLittleToBigEndian(buffer[24],buffer[25]);
				dosheader.OverlayNumber = Endiness.ShortLittleToBigEndian(buffer[26],buffer[27]);
				dosheader.OEMIdentifier = Endiness.ShortLittleToBigEndian(buffer[36],buffer[37]);
				dosheader.OEMInformation = Endiness.ShortLittleToBigEndian(buffer[38],buffer[39]);
				dosheader.SignatureOffset = Endiness.IntLittleToBigEndian(buffer[60],buffer[61],buffer[62],buffer[63]);
			}
			else
			{
				dosheader.LastPageOfFile = Endiness.ShortBigToLittleEndian(buffer[2],buffer[3]);
				dosheader.PagesInFile = Endiness.ShortBigToLittleEndian(buffer[4],buffer[5]);
				dosheader.Relocations = Endiness.ShortBigToLittleEndian(buffer[6],buffer[7]);
				dosheader.SizeOfHeader = Endiness.ShortBigToLittleEndian(buffer[8],buffer[9]);
				dosheader.MinParagraphs = Endiness.ShortBigToLittleEndian(buffer[10],buffer[11]);
				dosheader.MaxParagraphs = Endiness.ShortBigToLittleEndian(buffer[12],buffer[13]);
				dosheader.InitialSSValue = Endiness.ShortBigToLittleEndian(buffer[14],buffer[15]);
				dosheader.InitialSPValue = Endiness.ShortBigToLittleEndian(buffer[16],buffer[17]);
				dosheader.Checksum = Endiness.ShortBigToLittleEndian(buffer[18],buffer[19]);
				dosheader.InitialIPValue = Endiness.ShortBigToLittleEndian(buffer[20],buffer[21]);
				dosheader.InitialCSValue = Endiness.ShortBigToLittleEndian(buffer[22],buffer[23]);
				dosheader.RelocationsTable = Endiness.ShortBigToLittleEndian(buffer[24],buffer[25]);
				dosheader.OverlayNumber = Endiness.ShortBigToLittleEndian(buffer[26],buffer[27]);
				dosheader.OEMIdentifier = Endiness.ShortBigToLittleEndian(buffer[36],buffer[37]);
				dosheader.OEMInformation = Endiness.ShortBigToLittleEndian(buffer[38],buffer[39]);
				dosheader.SignatureOffset = Endiness.IntBigToLittleEndian(buffer[60],buffer[61],buffer[62],buffer[63]);
			}

			int i;
			byte [] reserved = new byte[8];
			for(i = 0;i < 8;i++)
			{
				reserved[i] = buffer[28+i];
			}
			dosheader.Reserved = reserved;
			reserved = new byte[20];
			for(i = 0;i < 20;i++)
			{
				reserved[i] = buffer[40+i];
			}
			dosheader.Reserved2 = reserved;

			int code_len = buffer.Length-(DOSHeader.LFANEW_OFFSET+4);
			reserved = new byte[code_len];
			for(i = 0;i < code_len;i++)
			{
				reserved[i] = buffer[DOSHeader.LFANEW_OFFSET+4+i];
			}
			dosheader.DOSStub = reserved;
		}

		private void ParsePEHeader()
		{
			PEHeader peheader = new PEHeader();

			peheader.MachineType = _machine_type;

			short stemp = 0;
			int itemp = 0;

			_reader.ReadShort(ref stemp);
			peheader.NumberOfSections = stemp;

			_reader.ReadInt(ref itemp);
			peheader.TimeDateStamp = itemp;
			
			_reader.ReadInt(ref itemp);
			peheader.PointerToSymbolTable = itemp;
			
			_reader.ReadInt(ref itemp);
			peheader.NumberOfSymbols = itemp;
			
			_reader.ReadShort(ref stemp);
			peheader.OptionalHeaderSize = stemp;
			
			_reader.ReadShort(ref stemp);
			peheader.Characteristics = stemp;
			_asm.PEHeader = peheader;
			_file_offset += PEHeader.PEHEADER_SIZE;
		}

		private void ParseStandardFields()
		{
			StandardFields standardfields = new StandardFields();

			short magic = 0;
			int ret = _reader.ReadShort(ref magic);
			if(ret != 2)
			{
				_log.WriteLine("Error reading standard fields magic number: " + ret);
				throw new CLELParserException("Error reading standard fields magic number: "+ret);
			}
			if(magic != StandardFields.PE32_MAGIC && magic != StandardFields.PE32PLUS_MAGIC)
			{
				_log.WriteLine("Error unsupported magic number in optional header: " + magic);
				throw new CLELParserException("Error unsupported magic number in optional header: "+magic);
			}
			int len = 0;
			if(magic == StandardFields.PE32_MAGIC)
				len = StandardFields.PE32_STANDARD_FIELDS_SIZE;
			else if(magic == StandardFields.PE32PLUS_MAGIC)
				len = StandardFields.PE32PLUS_STANDARD_FIELDS_SIZE;

			int itemp = 0;
			byte btemp = 0;

			standardfields.Magic = magic;

			_reader.ReadByte(ref btemp);
			standardfields.MajorLinkerVersion = btemp;

			_reader.ReadByte(ref btemp);
			standardfields.MinorLinkerVersion = btemp;
				
			_reader.ReadInt(ref itemp);
			standardfields.SizeOfCode = itemp;

			_reader.ReadInt(ref itemp);
			standardfields.SizeOfInitalizedData = itemp;

			_reader.ReadInt(ref itemp);
			standardfields.SizeOfUninitalizedData = itemp;

			_reader.ReadInt(ref itemp);
			standardfields.AddressOfEntryPoint = itemp;

			_reader.ReadInt(ref itemp);
			standardfields.BaseOfCode = itemp;

			if(magic == StandardFields.PE32_MAGIC)
			{
				_reader.ReadInt(ref itemp);
				standardfields.BaseOfData = itemp;
			}
			_asm.StandardFields = standardfields;
			_file_offset += len;
		}

		private void ParseNTSpecific()
		{
			NTSpecific ntspecific = new NTSpecific();
			
			short magic = _asm.StandardFields.Magic;
			int len = 0;
			if(magic == StandardFields.PE32_MAGIC)
				len = NTSpecific.PE32_NT_SPECIFIC_SIZE;
			else if(magic == StandardFields.PE32PLUS_MAGIC)
				len = NTSpecific.PE32PLUS_NT_SPECIFIC_SIZE;

			long ltemp = 0;
			int itemp = 0;
			short stemp = 0;
				
			if(magic == StandardFields.PE32_MAGIC)
			{
				_reader.ReadInt(ref itemp);
				ntspecific.ImageBase = (long)itemp;
			}
			else
			{
				_reader.ReadLong(ref ltemp);
				ntspecific.ImageBase = ltemp;
			}
			_reader.ReadInt(ref itemp);
			ntspecific.SectionAlignment = itemp;

			_reader.ReadInt(ref itemp);
			ntspecific.FileAlignment = itemp;
			
			_reader.ReadShort(ref stemp);	
			ntspecific.MajorOSVersion = stemp;

			_reader.ReadShort(ref stemp);	
			ntspecific.MinorOSVersion = stemp;

			_reader.ReadShort(ref stemp);	
			ntspecific.MajorImageVersion = stemp;

			_reader.ReadShort(ref stemp);	
			ntspecific.MinorImageVersion = stemp;

			_reader.ReadShort(ref stemp);	
			ntspecific.MajorSubSysVersion = stemp;

			_reader.ReadShort(ref stemp);	
			ntspecific.MinorSubSysVersion = stemp;

			_reader.ReadInt(ref itemp);	
			ntspecific.Win32VersionValue = itemp;

			_reader.ReadInt(ref itemp);
			ntspecific.SizeOfImage = itemp;

			_reader.ReadInt(ref itemp);	
			ntspecific.SizeOfHeaders = itemp;

			_reader.ReadInt(ref itemp);
			ntspecific.Checksum = itemp;

			_reader.ReadShort(ref stemp);
			ntspecific.Subsystem = stemp;

			_reader.ReadShort(ref stemp);	
			ntspecific.DLLCharacteristics = stemp;
				
			if(magic == StandardFields.PE32_MAGIC)
			{
				_reader.ReadInt(ref itemp);
				ntspecific.SizeOfStackReserve = (long)itemp;
				_reader.ReadInt(ref itemp);
				ntspecific.SizeOfStackCommit = (long)itemp;
				_reader.ReadInt(ref itemp);
				ntspecific.SizeOfHeapReserve = (long)itemp;
				_reader.ReadInt(ref itemp);
				ntspecific.SizeOfHeapCommit = (long)itemp;
			}
			else
			{
				_reader.ReadLong(ref ltemp);
				ntspecific.SizeOfStackReserve = ltemp;
				_reader.ReadLong(ref ltemp);
				ntspecific.SizeOfStackCommit = ltemp;
				_reader.ReadLong(ref ltemp);
				ntspecific.SizeOfHeapReserve = ltemp;
				_reader.ReadLong(ref ltemp);
				ntspecific.SizeOfHeapCommit = ltemp;
			}
			_reader.ReadInt(ref itemp);
			ntspecific.LoaderFlags = itemp;

			_reader.ReadInt(ref itemp);
			ntspecific.NumberOfDataDirectories = itemp;

			_asm.NTSpecific = ntspecific;
			_file_offset += len;
		}

		private void ParseDataDirectories()
		{
			DataDirectories datadirectories = new DataDirectories();
			
			int num_data_directories = _asm.NTSpecific.NumberOfDataDirectories;
			int len = num_data_directories*8;

			int i;
			int va = 0,size = 0;
			for(i = 0;i < num_data_directories;i++)
			{
				_reader.ReadInt(ref va);
				_reader.ReadInt(ref size);
				datadirectories.Add(new DataDirectory(va,size));
			}

			_asm.DataDirectories = datadirectories;
			_file_offset += len;
		}
		
		private void ParseSectionHeaders()
		{
			int num_sections = _asm.PEHeader.NumberOfSections;
			if(num_sections < 0)
			{
				_log.WriteLine("Error reading pe headers number of sections: " + num_sections);
				throw new CLELParserException("Error reading pe headers number of sections: "+num_sections);
			}
			SectionHeader [] sectionheaders = new SectionHeader[num_sections];
			
			int section_header_size = SectionHeader.SECTION_HEADER_SIZE;
			int len = num_sections*section_header_size;

			int i,j;
			SectionHeader sh;
			int section_header_name_size = SectionHeader.SECTION_HEADER_NAME_SIZE;
			byte [] name;
			byte btemp = 0;
			short stemp = 0;
			int itemp = 0;
			for(i = 0;i < num_sections;i++)
			{
				sh = new SectionHeader();
				name = new byte[section_header_name_size];

				for(j = 0;j < section_header_name_size;j++)
				{
					_reader.ReadByte(ref btemp);
					name[j] = btemp;
				}

				sh.Name = name;

				_reader.ReadInt(ref itemp);
				sh.VirtualSize = itemp;
				_reader.ReadInt(ref itemp);
				sh.VirtualAddress = itemp;
				_reader.ReadInt(ref itemp);
				sh.SizeOfRawData = itemp;
				_reader.ReadInt(ref itemp);
				sh.PointerToRawData = itemp;
				_reader.ReadInt(ref itemp);
				sh.PointerToRelocations = itemp;
				_reader.ReadInt(ref itemp);
				sh.PointerToLineNumbers = itemp;
				_reader.ReadShort(ref stemp);
				sh.NumberOfRelocations = stemp;
				_reader.ReadShort(ref stemp);
				sh.NumberOfLineNumbers = stemp;
				_reader.ReadInt(ref itemp);
				sh.Characteristics = itemp;

				sectionheaders[i] = sh;
			}
			_asm.SectionHeaders = sectionheaders;
			_file_offset += len;

			ReadHeadersPadding();
		}
		
		private void ReadHeadersPadding()
		{
			int padding_length = _asm.NTSpecific.SizeOfHeaders-_file_offset;
			if(padding_length > 0)
			{
				byte [] buffer = new byte[padding_length];
				int ret = _reader.Read(buffer,0,padding_length);
				if(ret != padding_length)
				{
					_log.WriteLine("Error reading headers padding: " + ret);
					throw new CLELParserException("Error reading headers padding: "+ret);
				}
				_file_offset += padding_length;
			}
		}
		
		private void ParseSections()
		{
			int i,j,k;
			int next;
			SectionHeader sh;
			SectionHeader [] sectionheaders = _asm.SectionHeaders;
			int len = sectionheaders.Length;
			//the spec does not say that the section headers are in order
			//therefore, loop through and find the next to parse
			bool [] section_not_parsed = new bool[len];
			//set all sections to not parsed
			for(i = 0;i < len;i++)
				section_not_parsed[i] = true;
			//for all sections
			for(i = 0;i < len;i++)
			{
				next = int.MaxValue;
				//index of next section to parse
				k = 0;
				//find the next section at the smallest offset that is not parsed
				for(j = 0;j < len;j++)
				{
					sh = sectionheaders[j];
					if(sh.PointerToRawData < next && section_not_parsed[j])
					{
						next = sh.PointerToRawData;
						//save index of section to parse
						k = j;
					}
				}
				//set section as parsed
				section_not_parsed[k] = false;
				//parse section
				ParseSection(sectionheaders[k]);
			}
		}

		private void ParseSection(SectionHeader sh)
		{
			String name = sh.NameAsString;
			switch(name)
			{
				case SectionHeader.TEXT_NAME: 	ParseTextSection(sh);
								break;
				case SectionHeader.RSRC_NAME: 	ParseRSRCSection(sh);
								break;
				case SectionHeader.RELOC_NAME:	ParseRELOCSection(sh);
								break;
				default:			throw new CLELParserException("Unknown section name: "+name);
			}
		}

		private void ParseTextSection(SectionHeader text_header)
		{
			if(text_header.PointerToRawData != _file_offset)
			{
				_log.WriteLine("Error text header and section alignment off: text header - " + 
					text_header.PointerToRawData + "  section offset - " + _file_offset);
				throw new CLELParserException("Error text header and section alignment off: text header - " + 
					text_header.PointerToRawData + "  section offset - " + _file_offset);
			}
			int total_size = text_header.SizeOfRawData;
			_rva = text_header.VirtualAddress;

			byte [] buffer = new byte[total_size];
			int ret = _reader.Read(buffer,0,total_size);
			if(ret != total_size)
			{
				_log.WriteLine("Error reading text section: " + ret);
				throw new CLELParserException("Error reading text section: "+ret);
			}

			CLELValidAssemblyTextParser text_parser = new CLELValidAssemblyTextParser(_asm,buffer,_reader.IsLittleEndian,_log,_rva);
			_asm.TextSection = text_parser.ParseTextSection();

			_file_offset += total_size;
		}

		private void ParseRSRCSection(SectionHeader rsrc_header)
		{
			if(rsrc_header.PointerToRawData != _file_offset)
			{
				_log.WriteLine("Error rsrc header and section alignment off: rsrc header - " + 
					rsrc_header.PointerToRawData + "  section offset - " + _file_offset);
				throw new CLELParserException("Error rsrc header and section alignment off: rsrc header - " + 
					rsrc_header.PointerToRawData + "  section offset - " + _file_offset);
			}
			int total_size = rsrc_header.SizeOfRawData;
			_rva = rsrc_header.VirtualAddress;
			
			byte [] buffer = new byte[total_size];
			int ret = _reader.Read(buffer,0,total_size);
			if(ret != total_size)
			{
				_log.WriteLine("Error reading rsrc section: " + ret);
				throw new CLELParserException("Error reading rsrc section: "+ret);
			}
			
			_asm.RSRCSection = buffer;

			_file_offset += total_size;
		}

		private void ParseRELOCSection(SectionHeader reloc_header)
		{
			if(reloc_header.PointerToRawData != _file_offset)
			{
				_log.WriteLine("Error reloc header and section alignment off: reloc header - " + 
					reloc_header.PointerToRawData + "  section offset - " + _file_offset);
				throw new CLELParserException("Error reloc header and section alignment off: reloc header - " + 
					reloc_header.PointerToRawData + "  section offset - " + _file_offset);
			}
			int total_size = reloc_header.SizeOfRawData;
			_rva = reloc_header.VirtualAddress;
			
			byte [] buffer = new byte[total_size];
			int ret = _reader.Read(buffer,0,total_size);
			if(ret != total_size)
			{
				_log.WriteLine("Error reading reloc section: " + ret);
				throw new CLELParserException("Error reading reloc section: "+ret);
			}
			
			_asm.RELOCSection = buffer;

			_file_offset += total_size;
		}
	}
}
